package com.iflytek.jcy.supervise.job.component.permission;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import cn.dev33.satoken.stp.StpUtil;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * 连续登录失败则锁定账号
 * 1. 登录时先调用getLockDuration获取剩余锁定时间
 * 2. 如果未锁定, 则正常走登录流程
 * 3. 如果登录失败则调用recordLoginFailed记录错误次数, 接口返回登录错误响应
 * 4. 如果登录成功则调用clearLoginFailed, 清空之前的登录失败记录
 *
 * @author <a href=mailto:ktyi@iflytek.com>伊开堂</a>
 * @date 2022/2/25
 */
@Component
public class AccountLocker {
    protected final Logger logger = LoggerFactory.getLogger(AccountLocker.class);

    @Value("${custom.security.attempt:5}")
    private int loginAttemptCount;
    @Value("${custom.security.duration:30}")
    private int loginAttemptDuration;
    @Value("${custom.security.lock:10}")
    private int lockDuration;

    private static final Map<String, List<LocalDateTime>> LOGIN_FAILED_RECORDS = new ConcurrentHashMap<>();

    /**
     * 获取用户剩余锁定时间
     *
     * @param username
     *
     * @return
     */
    public Duration getLockDuration(String username) {
        if (StringUtils.isBlank(username)) {
            return null;
        }

        if (StpUtil.isDisable(username)) {
            long leftSec = StpUtil.getDisableTime(username);
            if (leftSec <= 0) {
                return null;
            }

            return Duration.ofSeconds(leftSec);
        }

        return null;
    }

    /**
     * 记录登录失败
     *
     * @param username
     */
    public void recordLoginFailed(String username) {
        List<LocalDateTime> records = LOGIN_FAILED_RECORDS.getOrDefault(username, new ArrayList<>());
        LOGIN_FAILED_RECORDS.put(username, records);
        clearExpireRecords(records);
        records.add(LocalDateTime.now().plusMinutes(loginAttemptDuration));
        int size = CollectionUtils.size(records);
        logger.info("记录登录失败, username: {}, 当前总计: {}", username, size);

        // 连续登录失败次数超过限制, 则标记用户被禁用
        if (size >= loginAttemptCount) {
            StpUtil.disable(username, (long) lockDuration * 60);
            // 同时清空本轮的失败记录
            clearLoginFailed(username);
        }
    }

    // 清理过期记录
    private void clearExpireRecords(List<LocalDateTime> records) {
        if (CollectionUtils.isEmpty(records)) {
            return;
        }

        LocalDateTime now = LocalDateTime.now();
        records.removeIf(expireTime -> expireTime.isBefore(now));
    }

    /**
     * 清空登录失败记录
     *
     * @param username
     */
    public void clearLoginFailed(String username) {
        logger.info("清空登录失败记录, username: {}", username);
        LOGIN_FAILED_RECORDS.put(username, new ArrayList<>());
    }
}
